# -*- coding: utf-8 -*-
"""
Created on Mon Apr  9 08:27:31 2018

@author: lwm
"""

import pandas as pd
import os
import numpy as np
from collections import Counter
import difflib
import time

#用于阅读表格文件的路径的函数
def Get_FileList(dir, fileList):
    newDir = dir
    if os.path.isfile(dir):
        fileList.append(dir)
    elif os.path.isdir(dir):
        for s in os.listdir(dir):
            newDir = os.path.join(dir,s)
            Get_FileList(newDir, fileList)
    return fileList

#用于读取表格数据，并将数据转换为全为str类型的numpy.matrix数据的函数
#注意读取的文件格式要求是csv
def Get_table_data(fileList,file_id):
    file_path = fileList[file_id]
    try:
        with open(file_path, 'r', encoding = 'gb18030') as fp:
            original_data = pd.read_csv(fp, index_col=None, header=None)
    except:
        with open(file_path, 'r', encoding = 'utf8') as fp:
            original_data = pd.read_csv(fp, index_col=None, header=None)    
    #准备替换点所有的空单元格为字符串‘nan’，方便后续的处理
    space_char = 'nan'
    data = np.array(original_data).astype(str)#
    for i in range(len(data)):
        for j in range(len(data[i])):
            #删去了所有单元格的空格以及替换空表格为‘nan’
            data[i,j] = str(data[i,j]).replace(' ','').replace('nan',space_char)
    return data

#用于获取每个文件的字段名，为互补列识别规范数据的函数
def Get_field_list(fieldList,LocationList):
    #调用函数获取文件夹下所有的文件名称，并存储在一个list内
    fileList = Get_FileList('table_data_edit',[])
    for file_id in range(len(fileList)): 
        #调用函数获取一个表格
        table_original = Get_table_data(fileList,file_id)
        #获取表格文件的名称
        file_name = fileList[file_id].split('/')[-1]
        #遍历表格的第一列（标注的一列）用于得到字段名的位置
        location = [i for i in range(len(table_original[:,0])) if table_original[i,0] == '1.0']#这里1.0作为标注
        #提取出字段名的几行形成一个martrix，方便一会儿的处理
        field_name_martrix = table_original[location[0]:location[-1]+1,1:]
        #开始建立两个list，一个用于存储每个表格的文件名和字段名形成的list
        #另一个用于存储每个表格的文件名和字段名位置形成的list
        field_name_row = [file_name]#存入文件名
        loc_list = [file_name]#存入文件名
        #遍历由字段名形成的martrix，外面是列，里面是行
        #遍历是为了将一列多行的字段名转化为一个以‘-’链接的字段名
        for i in range(len(field_name_martrix[0,:])):#列的遍历
            #初始化一个list用于储存字段名
            field_name = []
            for varible in field_name_martrix[:,i]:#行的遍历
                #如果是空行就没有必要获取并加入字段名
                if varible  != 'nan':
                    field_name.append(varible)
            #如果有字段名再进行处理，否则全为空的一列是没有必要作为字段名储存下来的
            #开始把一列多行的字段名转换为以‘-’链接的字段名
            if field_name:
                field_name = "-".join(field_name)#以‘-’链接
                loc_list.append(i)#获取字段名的位置（即在表格的第几列）
                field_name_row.append(field_name)#获取转换后的字段名
        LocationList.append(loc_list)#将一个表中的完善的位置数据导入一个大的list内
        fieldList.append(field_name_row)#将一个表中的完善的字符段数据导入一个大的list内
    return fieldList , LocationList #返回

#获得一个模板用来表较筛选出互补列和额外的列（模板的定义是以列数最小的表格们，获取每一列最常出现的字段名作为模板的的该列的字段名）
#当然经过讨论，其实模板只需要最小列这一条件即可
#但是以防以后有什么需求和这样似乎更好理解的关系，还是保留最常出现的字段名的提取这一条件
def Get_template(List):
    #获取最小的长度的
    min_length = min([len(i) for i in List])
    #获取最小长度的表格的并存入list内
    List_Fortem = [i for i in List if len(i) == min_length]
    #将获取的最小列数的表格转化为一个martrix
    Martrix = np.array(List_Fortem)
    #建立一个list来储存模板
    Comparison_template = []
    for i in range(len(Martrix[0,1:])):
        column = Martrix[:,i+1]
        #获取最常出现的的字段名并将其添加入模板来形成模板
        Comparison_template.append(Counter(column).most_common()[0][0])
    return Comparison_template

#针对各个变量名获得其可能的属性
'''
下面的属性的获取的函数是最难考虑周全的地方，如果想对本代码继续提升准确度和广泛性的话
更改下面的规则是直接的方式
但是基于规则的属性获取方式总会出现与规则不相符，在规则考虑之外的情况发生
有时连特殊情况的识别都有些难以做到
这也是最困扰人的地方
'''
#属性的对应关系是    相同的列-'nom'    互补列‘com’    多余的列‘more’   
#当然与字段名行作比较的是模板                       
def Get_label(FieldList,Template):
    #建立一个总的list来储存可能的属性
    IdentificationList = []
    #遍历存储各个表格字段名的list
    for num in range(len(FieldList)):
        #对一个表格建立一个list来存储每个字段名可能的属性，并已将文件名放在了list的开头
        Identification = [FieldList[num][0]]
        #因为模板list内第一个元素不是文件名（筛选的模板怎么会有文件名）
        #所以会对存储字段名的list做一个切片，去掉文件名
        List = FieldList[num][1:]
        #因为要对循环的指针做处理，所以不选择用for来做循环，而是选择了while
        #初始化指针j
        j = 0
        #特殊情况检测用
        final_column_check = 0
        #遍历整个模板
        while j  < len(Template):
            #在指针达到模板最后一个元素之前的情况
            location1 = []
            location2 = []
            if j < len(Template) -1:
                #判别是否是与一个表格字段名行模板不同
                if Template[j] != List[j]:
                    #以模板该位置的字段名作为检测数据1
                    detection1 = Template[j]
                    #在字段名list内找寻是否有和检测数据1一样的字段名，若有便返回位置
                    location1 = [l1 for l1 in range(len(List)) if List[l1] == detection1]
                    #初始化检测用数据2与检测数据1的距离
                    extence = 1
                    #初始化测试，用以检测是否找到我了检测数据2
                    check1 = 0
                    #开始一个while循环
                    while check1 == 0:
                        #获取检测数据2
                        detection2 = Template[j+extence]
                        #获取检测数据2的位置
                        location2 = [l2 for l2 in range(len(List)) if List[l2] == detection2]
                        #如果找到了，则改变测试的值，结束while的循环
                        if location2:
                            check1 = 1
                        else:
                            #如果没有找到，则将检测用数据2与检测用数据1的距离+1
                            extence += 1
                            #如果检测数据2遍历整个字段名行都没找到，则说明后续全为互补列
                            if j+extence == len(Template):
                                for i in range(extence-1):
                                    Identification.append('com')
                                check1 = 1
                                final_column_check = 1
                                
                    if final_column_check == 0:                                                                                                               
                        #如果存在检测数据1的位置，那么就是单纯的多余列的情况
                        if location1:
                            #如果为单纯的多余列的情况，则检测数据1和检测数据2的距离为extence
                            if location2[0] == location1[0] + extence:
                                #获取增加的多余列的数量
                                distance = location1[0] - j
                                #按照数量将’more‘属性添加进属性list内
                                for i in range(distance):
                                    Identification.append('more') 
                                #删除原本字段名list内的多余列
                                List = List[:j] + List[location1[0]:]
                                #将'nom'属性添加进属性列表，因为删除原本的列表的多余列后，j的位置就到了检测数据1
                                #需要把检测数据1的属性给存储进去
                                Identification.append('nom')
                        #如果不存在检测数据1的位置，那么就是包含了互补列的情况                    
                        elif location2:   
                            #先看是单纯的互补列还是互补列和多余列混杂的情况
                            more_num = location2[0] - j - extence
                            #单纯的互补列的情况，因为没有多出来的列数
                            if more_num == 0:
                                for i in range(extence):
                                    #将’com‘属性添加进属性列表
                                    Identification.append('com')
                                #调整j的位置
                                j = j + extence                            
                                if List[j] == Template[j]:
                                    Identification.append('nom')
                                
                            #互补列和多余列的混杂情况，要把互补列和多余列提取出来
                            #主要是利用相似性来做的
                            elif more_num != 0:
                                #初始化相似性极限以及一个变量去检测是否正确区分
                                limit = 0.8
                                detection4 = 0
                                #将模板的指针位置到检测数据2的位置的数据提取出来作为相似性匹配的去匹配list
                                #同样的方式，将字段名list的指针位置到检测数据2所匹配到的地方提取出来作为被匹配的list
                                test_list = Template[j:j+extence]
                                Be_test_list = List[j:location2[0]]
                                #遍历将要被匹配的list
                                #在区分正确以前会一直循环
                                while detection4 == 0:
                                    #初始化以个处理list用于暂时存放每次循环的可能正确的答案，初始化互补列的列数
                                    Deal_list = []
                                    com_num = 0
                                    #创建一个list用于存储多余列的位置，方便一会儿删除
                                    more_location = []
                                    for k1 in range(len(Be_test_list)):
                                        #获取被匹配的值
                                        be_test = Be_test_list[k1]
                                        #创立一个变量来区分被匹配的值是不是互补列
                                        detection3 = 0
                                        #遍历要去匹配的list
                                        for k2 in range(len(test_list)):
                                            #获取要去匹配的值
                                            test = test_list[k2]
                                            #判断两个值的相似程度
                                            #如果相似程度为限制以上，则证明是互补列
                                            '''这里有一个纰漏，不能处理同义但是不相似的互补列，会将其错误分为多余列'''
                                            if difflib.SequenceMatcher(None, test,be_test).quick_ratio() > limit:
                                                #改变变量确定是互补列
                                                detection3 = 1
                                        #如果可能是正确的，添加给deal_list
                                        if detection3 == 1:
                                            Deal_list.append('com')
                                            com_num += 1
                                        else:
                                            Deal_list.append('more')
                                            more_location.append(j + k2)
                                    #如果区分错误，那么调整极限重新开始循环
                                    if len(more_location) < more_num:
                                        limit = limit*0.9
                                    elif len(more_location) > more_num:
                                        limit = limit*1.1
                                    #区分正确，则更改变量
                                    else:
                                        detection4 = 1
                                    #开始操作
                                    if detection4:    
                                        Identification = Identification + Deal_list
                                        List = [List[i] for i in range(len(List)) if i not in more_location]
                                        j = j + com_num    
                                        if List[j] == Template[j]:
                                            Identification.append('nom')
                                                       
                #字段名行与模板在同一位置值相同，则添加
                else:
                    Identification.append('nom')
                    
            
            #如果指针到了模板的最后一个元素        
            if j == len(Template)-1:
                #如果此时模板的长度和字段名行的长度一致了那么就不可能有多余列
                if j == len(List)-1 :
                    #不同，则代表是互补列
                    if Template[j] != List[j]:
                        Identification.append('com')
                    #如果是相同的，则说明是相同的列
                    else:
                        Identification.append('nom')
                #如果长度不相等，则要开始考虑为多余列与混杂列的情况
                else:
                    #如果不相等可能为混杂列和有多余列取代了原本的位置
                    #还是用相似度来进行判别
                    #不过先要看看有没有互补列，如果字段名后面所有元素没有和模板最后一个元素一致的
                    #则证明是混杂列，如果有一致的则说明是纯多余列
                    if Template[j] != List[j]:  
                        #获得相差的列数,和应得到的多余列的个数
                        length = len(List) - len(Template)
                        more_num = length
                        #提取模板的最后一个元素
                        To_test = Template[j]
                        #提取字段名行后面的所有元素
                        Be_test_list = List[j:]
                        #初始化一个限制
                        limit = 0.8
                        #创建一个变量去检测是否正确区分
                        detection5 = 0
                        while detection5 == 0:
                            #创建一个list来存储可能正确的结果
                            Possible = []     
                            #创建一个变量来存储more的数量
                            num = 0                                           
                            for i in range(len(Be_test_list)):
                                if difflib.SequenceMatcher(None, To_test,Be_test_list[i]).quick_ratio() > limit:
                                    #如果相似度是1时，则证明不是互补列和多余列而是相同列
                                    if difflib.SequenceMatcher(None, To_test,Be_test_list[i]).quick_ratio() == 1:                                        
                                        Possible.append('nom')
                                    #超过极限但又不是完全一致，则代表可能是互补列
                                    else:
                                        Possible.append('com')
                                #没超过极限，则代表可能是多余列
                                else:
                                    Possible.append('more')
                                    num += 1
                            #检验是否正确
                            #如果区分有误，则调整极限
                            if num < more_num:
                                limit = limit * 1.1
                            elif num > more_num:
                                limit = limit * 0.9
                            else:
                                detection5 = 1

                            if detection5:
                                #区分正确，直接获取给属性列表，不用对字段名行做其他处理
                                Identification = Identification+Possible
                            
                    #如果相同，则必然是纯的多余列
                    elif Template[j] == List[j]:
                        #多了多少列就代表多了几个多余列
                        distance = len(List) - j
                        Identification.append('nom')
                        for i in range(distance-1):
                            Identification.append('more')
            #指针自然加1                
            j += 1
        #属性列表合集列表获取该表格的属性列表
        IdentificationList.append(Identification)
        
    return IdentificationList


#用于获得互为互补列的几列
#主要方式是看在表格里的位置是否一致，再左右两列是否有相同的一列
#满足这两个情况， 则基本就是互补列
def Get_Com1(value,location,i,j,FieldList,LocationList,LocationList_copy):
    #创建一个列表来保存互为互补列的字段名，并且已经现将
    New_com = [[FieldList[i][0],value,LocationList_copy[i][j]]]
    for k in range(len(FieldList)):
        if k != i:
            #位置是否相同
            if LocationList[k][j] == location:  
                #根据指针的位置来选择判别左右列时判别哪一边
                #结尾只能取左边一列
                if j == len(FieldList[i])-1:
                    
                    if difflib.SequenceMatcher(None, FieldList[k][j-1],FieldList[i][j-1]).quick_ratio() > 0.5:
                        New_com.append([FieldList[k][0],FieldList[k][j],LocationList_copy[k][j]])
                    else:
                        print('wrong1')
                #开始只能取右边一列
                elif j == 0:
                    if difflib.SequenceMatcher(None, FieldList[k][j+1],FieldList[i][j+1]).quick_ratio() > 0.5:
                        New_com.append([FieldList[k][0],FieldList[k][j],LocationList_copy[k][j]])
                    else:
                        print('wrong1')        
                #中间时，可以取左右列
                else:
                    if difflib.SequenceMatcher(None, FieldList[k][j+1],FieldList[i][j+1]).quick_ratio() > 0.5 or difflib.SequenceMatcher(None, FieldList[k][j-1],FieldList[i][j-1]).quick_ratio() > 0.5:
                        New_com.append([FieldList[k][0],FieldList[k][j],LocationList_copy[k][j]])
                    else:
                        print('wrong2')
                        
            else:
                print('wrong3')
                
    return New_com

#用于寻找补充多出来的几列是不是互补列
#主要方式是看在表格里的位置是否一致，再左右两列是否有相同的一列
#满足这两个情况， 则基本就是互补列
'''这里有一个缺陷，就是，位置不同的多余列如果是互补列的话，就难考虑到'''
def Get_Com2(More,FieldList_copy,Final):
    More_value = {}
    More_file_name = {}
    Possible = []
    #现将所有的存取的多出来的列进行拆分，分别存储文件名和字段名
    for more in More:
        location = more[2]
        if location not in More_value.keys():
            More_value[location] = []
            More_file_name[location] = []
            More_value[location].append(more[1])
            More_file_name[location].append(more[0])
        else:
            More_value[location].append(more[1])
            More_file_name[location].append(more[0])
    #统计并返回该列最多出现的是什么字段名
    for key in More_value.keys():
        More_value[key] = Counter(More_value[key]).most_common()[0][0]
    #依据统计来的字段名来获取可能是字段名的数据并存储
    for more in More:
        location = more[2]
        if more[1] != More_value[location]:
            Possible.append(more)
    #针对每个可能的是字段名的数据进行判别
    for pos in Possible:
        Current = [pos]
        location = pos[2]
        More_file_name[location].remove(pos[0])
        file_list =  More_file_name[location][:]
        for row in FieldList_copy:
            if row[0] == pos[0]:
                com_row = row
        #老样子，比对原表的位置和原表的左右值
        for row in FieldList_copy:        
            if row[0] in file_list:
                if location < len(row)-2:
                    if row[location+2] == com_row[location+2] or row[location] == com_row[location]:                        
                        Current.append([row[0],row[location+1],location])
                elif location == 0:
                    if row[location+2] == com_row[location+2]:                        
                        Current.append([row[0],row[location+1],location])                    
                else:
                    if row[location] == com_row[location]:
                        Current.append([row[0],row[location+1],location])
        Final.append(Current)
    


#一个用于将每行多余的列所所提取出去的函数
#为后续只针对多余列与互补列的之间的比较做准备
def Clean_more(FieldList,IdentificationList,LocationList,LocationList_copy):
    #创建一个列表来存储多余列的信息
    More_list = []
    for i in range(len(IdentificationList)):
        #当删除多余列时，后面的单元格的位置会往前顺移，所以需要设置一个变量记录
        count = 0
        #创建一个列表存储多余列的位置
        del_list = []
        for j in range(len(IdentificationList[i])):
            #获取多余列信息的操作
            if IdentificationList[i][j] == 'more':
                value = FieldList[i][j]
                location = LocationList[i][j]
                del_list.append(j)
                count += 1
                more = [IdentificationList[i][0],value,location]
                More_list.append(more)
            #不是多余列的就只对列数进行操作
            else:
                if j > 0:
                    LocationList[i][j] = LocationList[i][j] - count
        #针对保存的位置删除各个位置的多余列的信息
        FieldList[i] = [FieldList[i][k] for k in range(len(FieldList[i])) if k not in del_list]
        LocationList[i] = [LocationList[i][k] for k in range(len(LocationList[i])) if k not in del_list]
        LocationList_copy[i] = [LocationList_copy[i][k] for k in range(len(LocationList_copy[i])) if k not in del_list]
        IdentificationList[i] = [IdentificationList[i][k] for k in range(len(IdentificationList[i])) if k not in del_list]
        
    return More_list

def Get_Final(FieldList,IdentificationList,LocationList,LocationList_copy,More,FieldList_copy):
    #创建一个列表来存储最终结果
    Final = []
    #遍历每个单元格的属性
    for i in range(len(IdentificationList)):
        for j in range(len(IdentificationList[i])):
            #属性是互补列会被获取信息
            if IdentificationList[i][j] == 'com':
                value = FieldList[i][j]
                location = LocationList[i][j]  
                #调用函数获取该互补列的互补列
                New_com = Get_Com1(value,location,i,j,FieldList,LocationList,LocationList_copy)
                #将结果存入
                Final.append(New_com)
    #调用函数对多余列进行处理，看多余列是否有互为互补列的
    Get_Com2(More,FieldList_copy,Final)
    
    return Final


#主函数
def Main_function():
    #调用函数获取字段名行组成的列表和位置组成的列表
    FieldList,LocationList = Get_field_list([],[])
    #备份一份，后续有使用的必要且方便对可能的问题做检验
    FieldList_copy = FieldList.copy()
    #调用函数获取模板
    Template = Get_template(FieldList)
    #获取属性列表组合的列表
    IdentificationList = Get_label(FieldList,Template)
    #备份一份，后续有使用的必要且方便对可能的问题做检验
    IdentificationList_copy = IdentificationList.copy()
    #备份一份，后续有使用的必要且方便对可能的问题做检验
    LocationList_copy = LocationList.copy()
    #调用函数清理原本的多余列，并将多余列存储进了列表里
    More = Clean_more(FieldList,IdentificationList,LocationList,LocationList_copy)
    #调用函数获取最终的结果
    Final = Get_Final(FieldList,IdentificationList,LocationList,LocationList_copy,More,FieldList_copy)   
     
    return Final,More,FieldList_copy,IdentificationList_copy



'''
上面全是函数
下面是代码主干
'''
start_time = time.time()
Final,More,FieldList_copy,IdentificationList= Main_function()
cost_time = time.time()-start_time
              